package core;
import javax.microedition.lcdui.*;
import colors.*;

import colors.MobileColor;

/**
 * <p>The Canvas on which the user's Worlds are drawn.
 * This class takes advantage of J2ME's image double-buffering system.
 * The user may call any of this class' draw functions, which appears to draw to
 * the canvas. Rather, it draws to a private image. This class is signaled to draw the
 * buffered image to itself only after the user's draw function has completed
 * creating the image.</p>
 * 
 * <p>This class is also the origin of all event processing except for timer events. 
 * By default, it handles when a key is pressed or released.
 * It currently also supports processing when a key is held down, as well as handles
 * for dealing with pointer events. A separate event exists for when a pointer is dragged.
 * All events call back to the <code>WorldMidlet</code> class to be processed.</p>
 * 
 * @author Shayne Caswell
 */
public class WorldCanvas extends Canvas {
	/** The image used to buffer the drawing of the World */
	private Image buffer; 
	
	/** The WorldMidlet that will process all event callbacks */
	private WorldMidlet wm;

	/**
	 * <p>Instantiates the image buffer and takes in a reference to
	 * the <code>WorldMidlet</code>.</p>
	 * 
	 * @param theWorld The <code>WorldMidlet</code> that uses this <code>WorldCanvas</code>.
	 */
	public WorldCanvas(WorldMidlet theWorld){
		this.buffer = Image.createImage(getWidth(), getHeight());
		this.wm = theWorld;
	}

	/**
	 * <p>Clear the contents of the this <code>WorldCanvas</code>,
	 * erasing the background to the given color.</p>
	 * 
	 * @param color The <code>MobileColor</code> to leave as the cleared background
	 */
	protected void clear(MobileColor color){
		Graphics gc = buffer.getGraphics();
		color.setToThisColor(gc);
		gc.fillRect(0, 0, getWidth(), getHeight());
	}

	/**
	 *  Performs the clear function, assuming that the caller
	 *  desired to clear the canvas with a white background.
	 */
	protected void clear(){
		this.clear(new White());
	}

	/**
	 * <p>Draw a circle on the image buffer, centered at the given point.
	 * The circle will have the given radius and be of the given <code>MobileColor</code>.
	 * This function draws a circle outline.</p>
	 * 
	 * @param center The <code>Posn</code> representing the circle's center
	 * @param radius The radius of this circle
	 * @param color The color to draw this circle with.
	 */
	public void drawCircle(Posn center, int radius, MobileColor color){
		Graphics gc = buffer.getGraphics();
		color.setToThisColor(gc);
		gc.drawArc(center.x - radius/2, center.y - radius/2, radius, radius, 0, 360);
	}

	/**
	 * <p>Draw a disk on the image buffer, centered at the given point.
	 * The disk will have the given radius and be of the given <code>MobileColor</code>.
	 * This function draws a filled in circle.</p>
	 * 
	 * @param center The <code>Posn</code> representing the disk's center.
	 * @param radius The radius of this disk.
	 * @param color The color to draw this disk with.
	 */
	public void drawDisk(Posn center, int radius, MobileColor color){
		Graphics gc = buffer.getGraphics();
		color.setToThisColor(gc);
		gc.fillArc(center.x - radius/2, center.y - radius/2, radius, radius, 0, 360);
	}

	/**
	 * <p>Draws a line on the image buffer, from the first point to the second.
	 * The line is drawn in the given color.</p>
	 * 
	 * @param p1 The point from which the line will be drawn.
	 * @param p2 The point to which the line will be drawn.
	 * @param color The color in which the line will be drawn.
	 */
	public void drawLine(Posn p1, Posn p2, MobileColor color){
		Graphics gc = buffer.getGraphics();
		color.setToThisColor(gc);
		gc.drawLine(p1.x, p1.y, p2.x, p2.y);
	}

	/**
	 * <p>Draws an outline of a rectangle on the image buffer.</p>
	 * 
	 * @param nw The <code>Posn</code> representing the position of the
	 * northwestern corner of the rectangle to be drawn.
	 * @param width The width of the rectangle to be drawn.
	 * @param height The height of the rectangle to be drawn.
	 * @param color The color in which to draw the rectangle.
	 */
	public void drawRect(Posn nw, int width, int height, MobileColor color){
		Graphics gc = buffer.getGraphics();
		color.setToThisColor(gc);
		gc.drawRect(nw.x, nw.y, width, height);
	}

	/**
	 * <p>Draws and fills a rectangle on the image buffer.</p>
	 * 
	 * @param nw The <code>Posn</code> representing the position of the
	 * northwestern corner of the rectangle to be drawn.
	 * @param width The width of the rectangle to be drawn.
	 * @param height The height of the rectangle to be drawn.
	 * @param color The color in which to draw the rectangle.
	 */
	public void fillRect(Posn nw, int width, int height, MobileColor color){
		Graphics gc = buffer.getGraphics();
		color.setToThisColor(gc);
		gc.fillRect(nw.x, nw.y, width, height);
	}

	/**
	 * Draws a String on the image buffer.
	 * 
	 * @param p The position at which this string should be drawn from.
	 * @param s The <code>String</code> to be drawn.
	 * @param color The MobileColor in which the string should be drawn.
	 */
	public void drawString(Posn p, String s, MobileColor color) {
		Graphics gc = buffer.getGraphics();
		color.setToThisColor(gc);
		gc.drawString(s, p.x, p.y, 0);
	}

	/**
	 * <p>Draws and fills in a triangle that lies between the
	 * given three vertexes. The triangle is drawn on the image buffer.</p>
	 * 
	 * @param a Position of the first vertex of the triangle
	 * @param b Position of the second vertex of the triangle
	 * @param c Position of the third vertex of the triangle
	 * @param color The MobileColor to draw this triangle with
	 */
	public void drawTriangle(Posn a, Posn b, Posn c, MobileColor color){
		Graphics gc = buffer.getGraphics();
		color.setToThisColor(gc);
		gc.fillTriangle(a.x, a.y, b.x, b.y, c.x, c.y);
	}

	/**
	 * <p>Event handler for when a key is pressed on the phone.
	 * Will create an instance of the <code>Button</code> class, with any 
	 * applicable information, such as the associated game action (if necessary) 
	 * and the name of the key that was pressed.</p>
	 * 
	 * <p>Uses the new instance of <code>Button</code> to call back the event to 
	 * the WorldMidlet and let the user define what happens as a result of the
	 * event.</p>
	 * 
	 * @param code The int value of the key that was pressed.
	 */
	protected void keyPressed(int code){
		String actionname;
		String keyname;
		int gameaction = getGameAction(code);
		
		switch (gameaction){
			case Canvas.UP:
				actionname = "up"; break;
			case Canvas.DOWN:
				actionname = "down"; break;
			case Canvas.LEFT:
				actionname = "left"; break;
			case Canvas.RIGHT:
				actionname = "right"; break;
			case Canvas.FIRE:
				actionname = "fire"; break;
			case Canvas.GAME_A:
				actionname = "a"; break;
			case Canvas.GAME_B:
				actionname = "b"; break;
			case Canvas.GAME_C:
				actionname = "c"; break;
			case Canvas.GAME_D:
				actionname = "d"; break;
			default: // any other value indicates there is no action associated
				actionname = "";
		} 
		
		if(code == 42){
			// rename (don't use "ASTERISK")
			keyname = "*";
		} else if(code == 35){
			// rename (don't use "POUND")
			keyname = "#";
		} else{
			// get the actual key name
			keyname = getKeyName(code);
		}
		
		// call back to the world with the info of the key that was pressed
		this.keyEventCallback(new Button(actionname, keyname));
	}

	/**
	 * <p>Event handler for when a key is released on the phone.
	 * Will create an instance of the <code>Button</code> class, that has no
	 * associated game action, but has a key name of <i>released</i>.</p>
	 * 
	 * <p>Uses the new instance of <code>Button</code> to call back the event to 
	 * the WorldMidlet and let the user define what happens as a result of the
	 * event.</p>
	 * 
	 * @param code The int value of the key that was pressed.
	 */
	protected void keyReleased(int code){
		this.keyEventCallback(new Button("released"));
	}

	/**
	 * <p>Event handler for when a pointer is pressed on the phone.
	 * Calls back to the <code>WorldMidlet</code> IF the device supports pointer events.</p>
	 * 
	 * @param x The x coordinate of the point where the pointer was pressed
	 * @param y The y coordinate of the point where the pointer was pressed
	 */
	protected void pointerPressed(int x, int y){
		if(this.hasPointerEvents()){
			this.pointerEventCallback(x, y);
		}
	}

	/**
	 * <p>Event handler for when a pointer is dragged on the phone.
	 * Calls back to the <code>WorldMidlet</code> IF the device supports pointer events
	 * and also supports pointer motion events.</p>
	 * 
	 * @param x The x coordinate of the point where the pointer was dragged to
	 * @param y The y coordinate of the point where the pointer was dragged to
	 */
	protected void pointerDragged(int x, int y){
		if(this.hasPointerMotionEvents()){
			this.dragEventCallback(x, y);
		}
	}

	/**
	 * <p>Event handler for when a pointer is released on the phone.
	 * Calls back to the <code>WorldMidlet</code> IF the device supports pointer events.</p>
	 * 
	 * @param x The x coordinate of the point where the pointer was released
	 * @param y The y coordinate of the point where the pointer was released
	 */
	protected void pointerReleased(int x, int y){
		if(this.hasPointerEvents()){
			this.pointerEventCallback(x, y);
		}
	}

	/**
	 * <p>The key event callback. Gives the <code>WorldMidlet</code> all necessary
	 * information to pass to the user for processing.</p>
	 * 
	 * @param keyPressed The <code>Button</code> representing the key that was
	 * pressed.
	 */
	protected void keyEventCallback(Button keyPressed){
		this.wm.processKeyEvent(keyPressed); 
	}

	/**
	 * <p>The pointer event callback. Gives the <code>WorldMidlet</code> a {@link Posn Posn}
	 * containing the coordinates of the point at which the pointer was pressed. The user may then
	 * access that <code>Posn</code> for further processing.</p>
	 * 
	 * @param x The x coordinate of the point pressed
	 * @param y The y coordinate of the point pressed
	 */
	protected void pointerEventCallback(int x, int y){
		this.wm.processPointerEvent(new Posn(x, y));
	}
	
	/**
	 * <p>The pointer drag event callback. Gives the <code>WorldMidlet</code> a {@link Posn Posn}
	 * containing the coordinates of the point at which the pointer was dragged to. The user may then
	 * access that <code>Posn</code> for further processing.</p>
	 * 
	 * @param x The x coordinate of the point pressed
	 * @param y The y coordinate of the point pressed
	 */
	protected void dragEventCallback(int x, int y){
		this.wm.processDragEvent(new Posn(x, y));
	}

	/**
	 * <p>Paint the currently buffered image onto this WorldCanvas.
	 * Creates a fresh buffer image for next set of draw calls to paint to.</p>
	 * 
	 * @param g The <code>Graphics</code> object to draw with.
	 */
	public void paint(Graphics g) {
		g.drawImage(buffer, 0, 0, 0);
	}
}